#include <stdlib.h>
#include "qe_array.h"
#include "qe_macros.h"
#include "qe_assert.h"
#include "qe_memory.h"
#include "qe_log.h"



#define QE_ARRAY_MIN_SIZE  (16)



#define qe_array_elem_len(array, i) ((array)->elem_size * (i))
#define qe_array_elem_pos(array, i) ((array)->data + qe_array_elem_len((array), (i)))
#define qe_array_elem_zero(array, pos, len) \
    (qe_memset(qe_array_elem_pos((array), pos), 0, qe_array_elem_len((array), len)))



static qe_uint qe_nearest_pow(qe_uint num)
{
    qe_uint n = 1;

    while (n < num)
        n <<= 1;

    return n;
}

static void qe_array_maybe_expand(qe_array *array, qe_uint len)
{
    qe_uint alloc = qe_array_elem_len(array, array->len + len);
    if (alloc > array->space) {
        alloc = qe_nearest_pow(alloc);
        alloc = qe_max(alloc, QE_ARRAY_MIN_SIZE);
        array->data = qe_realloc(array->data, alloc);
        array->space = alloc;
    }
}

qe_array *qe_array_new(qe_uint elem_size)
{
    return qe_array_new_sized(elem_size, 0);
}

qe_array *qe_array_new_sized(qe_uint elem_size, qe_uint reserved_size)
{
    qe_array *array = qe_malloc(sizeof(qe_array));
    qe_assert(array != QE_NULL);

    array->data      = QE_NULL;
    array->len       = 0;
    array->elem_size = elem_size;
    array->space     = 0;

    if (reserved_size) {
        qe_array_maybe_expand(array, reserved_size);
    }

    return array;
}

char *qe_array_free(qe_array *array, qe_bool free_segment)
{
    char *segment;

    if (!array)
        return QE_NULL;

    if (free_segment) {
        qe_free(array->data);
        segment = QE_NULL;
    } else {
        segment = (char *)array->data;
    }

    qe_free(array);

    return segment;
}

/**
 * @brief Append elements to array
 * 
 * @param[in] array : the array 
 * @param[in] data  : elements point
 * @param[in] len   : elements count
 *  
 * @return qe_array* 
 */
qe_array *qe_array_append(qe_array *array, const void *data, qe_uint len)
{
    qe_array_maybe_expand(array, len);

    qe_memcpy(qe_array_elem_pos(array, array->len), (void *)data, qe_array_elem_len(array, len));

    array->len += len;

    return array;
}

/**
 * @brief Prepend elements to array
 * 
 * @param[in] array : the array 
 * @param[in] data  : elements point
 * @param[in] len   : elements count
 *  
 * @return qe_array* 
 */
qe_array *qe_array_prepend(qe_array *array, const void *data, qe_uint len)
{
    qe_array_maybe_expand(array, len);

    qe_memmove(qe_array_elem_pos(array, len), 
               qe_array_elem_pos(array, 0),
               qe_array_elem_len(array, array->len));

    qe_memcpy(qe_array_elem_pos(array, 0), (void *)data, 
        qe_array_elem_len(array, len));

    array->len += len;

    return array;
}

/**
 * @brief Insert elements to array
 * 
 * @param[in] array : the array
 * @param[in] index : insert index
 * @param[in] data  : elements point
 * @param[in] len   : elements count
 * @return qe_array* 
 */
qe_array *qe_array_insert(qe_array *array, qe_uint index, const void *data, 
    qe_uint len)
{
    qe_array_maybe_expand(array, len);

    qe_memmove(qe_array_elem_pos(array, len + index),
               qe_array_elem_pos(array, index),
               qe_array_elem_len(array, array->len - index));

    qe_memcpy(qe_array_elem_pos(array, index), (void *)data, qe_array_elem_len(array, len));

    array->len += len;

    return array;
}

/**
 * @brief Set array size
 * 
 * @param array[in]  : array
 * @param length[in] : elements count
 * @return qe_array* 
 */
qe_array *qe_array_set_size(qe_array *array, qe_uint length)
{
    if (length > array->len) {
        qe_array_maybe_expand(array, length - array->len);
    } else if (length < array->len) {
        qe_array_elem_zero(array, length, array->len - length);
    }

    array->len = length;

    return array;
}

/**
 * @brief Remove a element from array
 * 
 * @param array[in] : array
 * @param index[in] : remove index
 * @return qe_array* 
 */
qe_array *qe_array_remove_index(qe_array *array, qe_uint index)
{
    if (!array)
        return QE_NULL;

    if (index >= array->len)
        return QE_NULL;

    if (index != array->len - 1) {
        qe_memmove(qe_array_elem_pos(array, index),
	               qe_array_elem_pos(array, index + 1),
	               qe_array_elem_len(array, array->len - index - 1));
    }

    array->len -= 1;

    return array;
}

/**
 * @brief Remove elements from array
 * 
 * @param array[in]   : array
 * @param index[in]   : remove index
 * @param length[out] : remove length
 * @return qe_array* 
 */
qe_array *qe_array_remove_range(qe_array *array, qe_uint index, qe_uint length)
{
    if (!array)
        return QE_NULL;
    
    if (index > array->len)
        return QE_NULL;

    if ((index + length) > array->len)
        return QE_NULL;

    if (index + length != array->len) {
        qe_memmove(qe_array_elem_pos(array, index), 
                   qe_array_elem_pos(array, index + length),
                   qe_array_elem_len(array, array->len - (index + length)));
    }

    array->len -= length;

    return array;
}

/**
 * @brief Sort array with compare function
 * 
 * @param array[in]   : array
 * @param compare[in] : compare function
 */
void qe_array_sort(qe_array *array, int (*compare)(const void *, const void *))
{
    if (!array)
        return;

    qsort(array->data, array->len, array->elem_size, compare);
}